home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue46 / ComCorn / ServMain.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1999-05-05  |  10.2 KB  |  345 lines

  1. unit ServMain;
  2.  
  3. interface
  4.  
  5. uses
  6.   ActiveX, MtsObj, Mtx, ComObj, TTTServer_TLB;
  7.  
  8. type
  9.   PGameData = ^TGameData;
  10.   TGameData = array[1..3, 1..3] of Byte;
  11.  
  12.   TGameServer = class(TMtsAutoObject, IGameServer)
  13.   private
  14.     procedure CalcComputerMove(GameData: PGameData; Skill: SkillLevels;
  15.       var X, Y: Integer);
  16.     function CalcGameStatus(GameData: PGameData): GameResults;
  17.     function GetSharedPropertyGroup: ISharedPropertyGroup;
  18.     procedure CheckCallerSecurity;
  19.   protected
  20.     procedure NewGame(out GameID: Integer); safecall;
  21.     procedure ComputerMove(GameID: Integer; SkillLevel: SkillLevels; out X,
  22.       Y: Integer; out GameRez: GameResults); safecall;
  23.     procedure PlayerMove(GameID, X, Y: Integer; out GameRez: GameResults);
  24.       safecall;
  25.   end;
  26.  
  27. implementation
  28.  
  29. uses ComServ, Windows, SysUtils;
  30.  
  31. const
  32.   GameDataStr = 'TTTGameData%d';
  33.   EmptySpot = 0;
  34.   PlayerSpot = $1;
  35.   ComputerSpot = $2;
  36.  
  37. function TGameServer.GetSharedPropertyGroup: ISharedPropertyGroup;
  38. var
  39.   SPGMgr: ISharedPropertyGroupManager;
  40.   LockMode, RelMode: Integer;
  41.   Exists: WordBool;
  42. begin
  43.   if ObjectContext = nil then
  44.     raise Exception.Create('Failed to obtain object context');
  45.   // Create shared property group for this object
  46.   OleCheck(ObjectContext.CreateInstance(CLASS_SharedPropertyGroupManager,
  47.     ISharedPropertyGroupManager, SPGMgr));
  48.   LockMode := LockSetGet;
  49.   RelMode := Process;
  50.   Result := SPGMgr.CreatePropertyGroup('DelphiTTT', LockMode, RelMode, Exists);
  51.   if Result = nil then
  52.     raise Exception.Create('Failed to obtain property group');
  53. end;
  54.  
  55. procedure TGameServer.NewGame(out GameID: Integer);
  56. var
  57.   SPG: ISharedPropertyGroup;
  58.   SProp: ISharedProperty;
  59.   Exists: WordBool;
  60.   GameData: OleVariant;
  61. begin
  62.   // Use caller's role to validate security
  63.   CheckCallerSecurity;
  64.   // Get shared property group for this object
  65.   SPG := GetSharedPropertyGroup;
  66.   // Create or retrieve NextGameID shared property
  67.   SProp := SPG.CreateProperty('NextGameID', Exists);
  68.   if Exists then GameID := SProp.Value
  69.   else GameID := 0;
  70.   // Increment and store NextGameID shared property
  71.   SProp.Value := GameID + 1;
  72.   // Create game data array
  73.   GameData := VarArrayCreate([1, 3, 1, 3], varByte);
  74.   SProp := SPG.CreateProperty(Format(GameDataStr, [GameID]), Exists);
  75.   SProp.Value := GameData;
  76.   SetComplete;
  77. end;
  78.  
  79. procedure TGameServer.ComputerMove(GameID: Integer;
  80.   SkillLevel: SkillLevels; out X, Y: Integer; out GameRez: GameResults);
  81. var
  82.   Exists: WordBool;
  83.   PropVal: OleVariant;
  84.   GameData: PGameData;
  85.   SProp: ISharedProperty;
  86. begin
  87.   // Get game data shared property
  88.   SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]),
  89.     Exists);
  90.   // Get game data array and lock it for more efficient access
  91.   PropVal := SProp.Value;
  92.   GameData := PGameData(VarArrayLock(PropVal));
  93.   try
  94.     // If game isn't over, then let computer make a move
  95.     GameRez := CalcGameStatus(GameData);
  96.     if GameRez = grInProgress then
  97.     begin
  98.       CalcComputerMove(GameData, SkillLevel, X, Y);
  99.       // Save away new game data array
  100.       SProp.Value := PropVal;
  101.       // Check for end of game
  102.       GameRez := CalcGameStatus(GameData);
  103.     end;
  104.   finally
  105.     VarArrayUnlock(PropVal);
  106.   end;
  107.   SetComplete;
  108. end;
  109.  
  110. procedure TGameServer.PlayerMove(GameID, X, Y: Integer;
  111.   out GameRez: GameResults);
  112. var
  113.   Exists: WordBool;
  114.   PropVal: OleVariant;
  115.   GameData: PGameData;
  116.   SProp: ISharedProperty;
  117. begin
  118.   // Get game data shared property
  119.   SProp := GetSharedPropertyGroup.CreateProperty(Format(GameDataStr, [GameID]),
  120.     Exists);
  121.   // Get game data array and lock it for more efficient access
  122.   PropVal := SProp.Value;
  123.   GameData := PGameData(VarArrayLock(PropVal));
  124.   try
  125.     // Make sure game isn't over
  126.     GameRez := CalcGameStatus(GameData);
  127.     if GameRez = grInProgress then
  128.     begin
  129.       // If spot isn't empty, raise exception
  130.       if GameData[X, Y] <> EmptySpot then
  131.         raise Exception.Create('Spot is occupied!');
  132.       // Allow move
  133.       GameData[X, Y] := PlayerSpot;
  134.       // Save away new game data array
  135.       SProp.Value := PropVal;
  136.       // Check for end of game
  137.       GameRez := CalcGameStatus(GameData);
  138.     end;
  139.   finally
  140.     VarArrayUnlock(PropVal);
  141.   end;
  142.   SetComplete;
  143. end;
  144.  
  145. function TGameServer.CalcGameStatus(GameData: PGameData): GameResults;
  146. var
  147.   I, J: Integer;
  148. begin
  149.   // First check for a winner
  150.   if GameData[1, 1] <> EmptySpot then
  151.   begin
  152.     // Check top row, left column, and top left to bottom right diagonal for win
  153.     if ((GameData[1, 1] = GameData[1, 2]) and (GameData[1, 1] = GameData[1, 3])) or
  154.       ((GameData[1, 1] = GameData[2, 1]) and (GameData[1, 1] = GameData[3, 1])) or
  155.       ((GameData[1, 1] = GameData[2, 2]) and (GameData[1, 1] = GameData[3, 3])) then
  156.     begin
  157.       Result := GameData[1, 1] + 1; // Game result is spot ID + 1
  158.       Exit;
  159.     end;
  160.   end;
  161.   if GameData[3, 3] <> EmptySpot then
  162.   begin
  163.     // Check bottom row and right column for win
  164.     if ((GameData[3, 3] = GameData[3, 2]) and (GameData[3, 3] = GameData[3, 1])) or
  165.       ((GameData[3, 3] = GameData[2, 3]) and (GameData[3, 3] = GameData[1, 3])) then
  166.     begin
  167.       Result := GameData[3, 3] + 1; // Game result is spot ID + 1
  168.       Exit;
  169.     end;
  170.   end;
  171.   if GameData[2, 2] <> EmptySpot then
  172.   begin
  173.     // Check middle row, middle column, and bottom left to top right diagonal for win
  174.     if ((GameData[2, 2] = GameData[2, 1]) and (GameData[2, 2] = GameData[2, 3])) or
  175.       ((GameData[2, 2] = GameData[1, 2]) and (GameData[2, 2] = GameData[3, 2])) or
  176.       ((GameData[2, 2] = GameData[3, 1]) and (GameData[2, 2] = GameData[1, 3])) then
  177.     begin
  178.       Result := GameData[2, 2] + 1; // Game result is spot ID + 1
  179.       Exit;
  180.     end;
  181.   end;
  182.   // Finally, check for game still in progress
  183.   for I := 1 to 3 do
  184.     for J := 1 to 3 do
  185.       if GameData[I, J] = 0 then
  186.       begin
  187.         Result := grInProgress;
  188.         Exit;
  189.       end;
  190.   // If we get here, then we've tied
  191.   Result := grTie;
  192. end;
  193.  
  194. procedure TGameServer.CalcComputerMove(GameData: PGameData;
  195.   Skill: SkillLevels; var X, Y: Integer);
  196. type
  197.   // Used to scan for possible moves by either row, column, or diagonal line
  198.   TCalcType = (ctRow, ctColumn, ctDiagonal);
  199.   // mtWin = one move away from win, mtBlock = opponent is one move away from
  200.   // win, mtOne = I occupy one other spot in this line, mtNew = I occupy no
  201.   // spots on this line
  202.   TMoveType = (mtWin, mtBlock, mtOne, mtNew);
  203. var
  204.   CurrentMoveType: TMoveType;
  205.  
  206.   function DoCalcMove(CalcType: TCalcType; Position: Integer): Boolean;
  207.   var
  208.     RowData, I, J, CheckTotal: Integer;
  209.     PosVal, Mask: Byte;
  210.   begin
  211.     Result := False;
  212.     RowData := 0;
  213.     X := 0;
  214.     Y := 0;
  215.     if CalcType = ctRow then
  216.     begin
  217.       I := Position;
  218.       J := 1;
  219.     end
  220.     else if CalcType = ctColumn then
  221.     begin
  222.       I := 1;
  223.       J := Position;
  224.     end
  225.     else begin
  226.       I := 1;
  227.       case Position of
  228.         1: J := 1; // scanning from top left to bottom right
  229.         2: J := 3; // scanning from top right to bottom left
  230.       else
  231.         Exit;   // bail; only 2 diagonal scans
  232.       end;
  233.     end;
  234.     // Mask masks off Player or Computer bit, depending on whether we're thinking
  235.     // offensively or defensively.  Checktotal determines whether that is a row
  236.     // we need to move into.
  237.     case CurrentMoveType of
  238.       mtWin:
  239.         begin
  240.           Mask := PlayerSpot;
  241.           CheckTotal := 4;
  242.         end;
  243.       mtNew:
  244.         begin
  245.           Mask := PlayerSpot;
  246.           CheckTotal := 0;
  247.         end;
  248.       mtBlock:
  249.         begin
  250.           Mask := ComputerSpot;
  251.           CheckTotal := 2;
  252.         end;
  253.     else
  254.       begin
  255.         Mask := 0;
  256.         CheckTotal := 2;
  257.       end;
  258.     end;
  259.     // loop through all lines in current CalcType
  260.     repeat
  261.       // Get status of current spot (X, O, or empty)
  262.       PosVal := GameData[I, J];
  263.       // Save away last empty spot in case we decide to move here
  264.       if PosVal = 0 then
  265.       begin
  266.         X := I;
  267.         Y := J;
  268.       end
  269.       else
  270.         // If spot isn't empty, then add masked value to RowData
  271.         Inc(RowData, (PosVal and not Mask));
  272.       if (CalcType = ctDiagonal) and (Position = 2) then
  273.       begin
  274.         Inc(I);
  275.         Dec(J);
  276.       end
  277.       else begin
  278.         if CalcType in [ctRow, ctDiagonal] then Inc(J);
  279.         if CalcType in [ctColumn, ctDiagonal] then Inc(I);
  280.       end;
  281.     until (I > 3) or (J > 3);
  282.     // If RowData adds up, then we must block or win, depending on whether we're
  283.     // thinking offensively or defensively.
  284.     Result := (X <> 0) and (RowData = CheckTotal);
  285.     if Result then
  286.     begin
  287.       GameData[X, Y] := ComputerSpot;
  288.       Exit;
  289.     end;
  290.   end;
  291.  
  292. var
  293.   A, B, C: Integer;
  294. begin
  295.   if Skill = slAwake then
  296.   begin
  297.     // First look to win the game, next look to block a win
  298.     for A := Ord(mtWin) to Ord(mtBlock) do
  299.     begin
  300.       CurrentMoveType := TMoveType(A);
  301.       for B := Ord(ctRow) to Ord(ctDiagonal) do
  302.         for C := 1 to 3 do
  303.           if DoCalcMove(TCalcType(B), C) then Exit;
  304.     end;
  305.     // Next look to take the center of the board
  306.     if GameData[2, 2] = 0 then
  307.     begin
  308.       GameData[2, 2] := ComputerSpot;
  309.       X := 2;
  310.       Y := 2;
  311.       Exit;
  312.     end;
  313.     // Next look for the most advantageous position on a line
  314.     for A := Ord(mtOne) to Ord(mtNew) do
  315.     begin
  316.       CurrentMoveType := TMoveType(A);
  317.       for B := Ord(ctRow) to Ord(ctDiagonal) do
  318.         for C := 1 to 3 do
  319.           if DoCalcMove(TCalcType(B), C) then Exit;
  320.     end;
  321.   end;
  322.   // Finally (or if skill level is unconscious), just find the first open place
  323.   for A := 1 to 3 do
  324.     for B := 1 to 3 do
  325.       if GameData[A, B] = 0 then
  326.       begin
  327.         GameData[A, B] := ComputerSpot;
  328.         X := A;
  329.         Y := B;
  330.         Exit;
  331.       end;
  332. end;
  333.  
  334. procedure TGameServer.CheckCallerSecurity;
  335. begin
  336.   // Just for fun, only allow those in the "TTT" role to play the game.
  337.   if IsSecurityEnabled and not IsCallerInRole('TTT') then
  338.     raise Exception.Create('Only those in the TTT role can play tic-tac-toe');
  339. end;
  340.  
  341. initialization
  342.   TAutoObjectFactory.Create(ComServer, TGameServer, Class_GameServer,
  343.     ciMultiInstance, tmApartment);
  344. end.
  345.